import winreg
import wx.adv
from pycaw.pycaw import AudioUtilities, ISimpleAudioVolume
from core.config import Config
import win32process,win32gui
import psutil
import core.vkMap as vkMap
import datetime
import requests
import json
import pythoncom
import ctypes
import os
import subprocess
from ctypes import wintypes
import sys

from core.model import WindowInfo

# 定义 NtSuspendProcess 和 NtResumeProcess 的类型
NtSuspendProcess = ctypes.WINFUNCTYPE(wintypes.LONG, wintypes.HANDLE)
NtResumeProcess = ctypes.WINFUNCTYPE(wintypes.LONG, wintypes.HANDLE)

# 加载 ntdll.dll
ntdll = ctypes.WinDLL("ntdll")
nt_suspend_process = NtSuspendProcess(("NtSuspendProcess", ntdll))
nt_resume_process = NtResumeProcess(("NtResumeProcess", ntdll))

def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

def run_as_admin():
    if is_admin():
        print("Already running as administrator.")
    else:
        print("Requesting administrator privileges...")
        ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)

# 检查pssuspend64是否存在
def check_pssuspend_exists():
    """检查pssuspend64.exe是否在程序根目录下存在"""
    pssuspend_path =os.path.join(Config.root_path,"pssuspend64.exe")
    return os.path.exists(pssuspend_path)

# 使用pssuspend64冻结进程
def suspend_process_enhanced(pid):
    """使用pssuspend64.exe冻结指定PID的进程"""
    try:
        pssuspend_path = os.path.join(Config.root_path,"pssuspend64.exe")
        result = subprocess.run([pssuspend_path, str(pid)], 
                               stdout=subprocess.PIPE, 
                               stderr=subprocess.PIPE,
                               text=True,
                               creationflags=subprocess.CREATE_NO_WINDOW)
        if result.returncode != 0:
            raise RuntimeError(f"pssuspend64执行失败: {result.stderr}")
    except Exception as e:
        raise RuntimeError(f"无法使用pssuspend64冻结进程: {str(e)}")

# 使用pssuspend64解冻进程
def resume_process_enhanced(pid):
    """使用pssuspend64.exe解冻指定PID的进程"""
    try:
        pssuspend_path = os.path.join(Config.root_path,"pssuspend64.exe")
        result = subprocess.run([pssuspend_path, "-r", str(pid)], 
                               stdout=subprocess.PIPE, 
                               stderr=subprocess.PIPE,
                               text=True,
                               creationflags=subprocess.CREATE_NO_WINDOW)
        if result.returncode != 0:
            raise RuntimeError(f"pssuspend64执行失败: {result.stderr}")
    except Exception as e:
        raise RuntimeError(f"无法使用pssuspend64解冻进程: {str(e)}")

# 冻结进程 (Suspend Process)
def suspend_process(pid):
    """冻结指定PID的进程"""
    # 如果启用了增强冻结且pssuspend64存在，则使用pssuspend64
    if hasattr(Config, 'enhanced_freeze') and Config.enhanced_freeze and check_pssuspend_exists() and is_admin():
        return suspend_process_enhanced(pid)
    
    process_handle = ctypes.windll.kernel32.OpenProcess(0x001F0FFF, False, pid)  # PROCESS_ALL_ACCESS
    if not process_handle:
        raise RuntimeError(f"无法打开进程，PID: {pid}")
    
    try:
        nt_status = nt_suspend_process(process_handle)
        if nt_status != 0:
            raise RuntimeError(f"NtSuspendProcess 调用失败，状态码: {nt_status}")
    finally:
        ctypes.windll.kernel32.CloseHandle(process_handle)

# 杀死进程 (Kill Process)
def kill_process(pid):
    """杀死指定PID的进程"""
    try:
        proc = psutil.Process(pid)
        proc.terminate()  # 优雅终止
        # proc.kill()  # 强制终止
        # print(f"已杀死进程 {pid}")
    except psutil.NoSuchProcess:
        print(f"进程 {pid} 不存在")
    except psutil.AccessDenied:
        print(f"没有权限杀死进程 {pid}")
    except Exception as e:
        print(f"kill {pid} fail {e}")

# 解冻进程 (Resume Process)
def resume_process(pid):
    """解冻指定PID的进程"""
    # 如果启用了增强冻结且pssuspend64存在，则使用pssuspend64
    if hasattr(Config, 'enhanced_freeze') and Config.enhanced_freeze and check_pssuspend_exists() and is_admin():
        return resume_process_enhanced(pid)
    
    process_handle = ctypes.windll.kernel32.OpenProcess(0x001F0FFF, False, pid)  # PROCESS_ALL_ACCESS
    if not process_handle:
        raise RuntimeError(f"无法打开进程，PID: {pid}")
    
    try:
        nt_status = nt_resume_process(process_handle)
        if nt_status != 0:
            raise RuntimeError(f"NtResumeProcess 调用失败，状态码: {nt_status}")
    finally:
        ctypes.windll.kernel32.CloseHandle(process_handle)

def checkUpdate():
    requests.packages.urllib3.disable_warnings()
    # 获取最新版本信息
    try:
        response = requests.get("https://ivanhanloth.github.io/Boss-Key/releases.json", verify=False,timeout=10)
        
        if response.status_code != 200:
            raise Exception("无法检查更新")
    except:
        raise Exception("无法检查更新")

    releases = json.loads(response.text)

    for release in releases:
        release['published_at'] = datetime.datetime.strptime(release['published_at'], "%Y-%m-%dT%H:%M:%SZ")
    
    # 找到最新的版本
    latest_release = max(releases, key=lambda x: x['published_at'])
    
    return latest_release

def addStartup(program_name, program_path):
    """
    将程序添加到开机自启动

    :param program_name: 注册表中的程序名称
    :param program_path: 程序的完整路径
    """
    # 打开注册表中的自启动项
    key = winreg.HKEY_CURRENT_USER
    key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
    
    try:
        # 打开注册表键
        registry_key = winreg.OpenKey(key, key_path, 0, winreg.KEY_WRITE)
        # 设置注册表项
        winreg.SetValueEx(registry_key, program_name, 0, winreg.REG_SZ, program_path)
        # 关闭注册表键
        winreg.CloseKey(registry_key)
        return True
    except WindowsError as e:
        return False

def removeStartup(program_name):
    """
    从开机自启动中移除程序

    :param program_name: 注册表中的程序名称
    """
    # 打开注册表中的自启动项
    key = winreg.HKEY_CURRENT_USER
    key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
    
    try:
        # 打开注册表键
        registry_key = winreg.OpenKey(key, key_path, 0, winreg.KEY_WRITE)
        # 删除注册表项
        winreg.DeleteValue(registry_key, program_name)
        # 关闭注册表键
        winreg.CloseKey(registry_key)
        return True
    except WindowsError as e:
        return False
    
def checkStartup(name: str, file_path: str):
    """
    Check if the startup key exists and if it points to the correct file path

    returns True if the key exists and points to the correct file path
    """
    key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0, winreg.KEY_READ)
    try:
        existing_value, _ = winreg.QueryValueEx(key, name)

        if existing_value == file_path:
            return True
        else:
            return False
    except WindowsError:
        return False
    
def changeMute(hwnd,flag=1):
    """
    flag=1 mute
    """
    try:
        # 初始化 COM 环境
        pythoncom.CoInitialize()
        process=win32process.GetWindowThreadProcessId(int(hwnd)) # 获取窗口句柄对应的进程ID
        process=psutil.Process(process[1]) # 获取进程对象
        # 获取所有音频会话
        sessions = AudioUtilities.GetAllSessions()
        for session in sessions:
            volume = session.SimpleAudioVolume
            if session.Process:
                if session.Process.ppid == process.ppid() or session.Process.exe() == process.exe() or session.Process.pid == process.pid or session.Process.ppid == process.pid:
                    volume.SetMute(flag, None)
                    break
    except Exception as e:
        print("tools-changeMute: ",e)
    finally:
        # 释放 COM 环境
        try:    
            pythoncom.CoUninitialize()
        except:
            pass

def remove_duplicates(input_list: list[WindowInfo], key_name):
    """
    Remove duplicates from a list while preserving the order.
    
    input_list: list, the list from which to remove duplicates
    returns: list, the list without duplicates
    """
    seen = set()
    output_list = []
    for item in input_list:
        if item.to_dict()[key_name] not in seen:
            seen.add(item.to_dict()[key_name])
            output_list.append(item)
    return output_list

def hwnd2processName(hwnd):
    """
    从窗口句柄获取进程名称
    返回None为不存在的窗口
    """
    try:
        pid = win32process.GetWindowThreadProcessId(hwnd)[1]
        process_name = psutil.Process(pid).name()
    except:
        process_name=None
    return process_name

def hwnd2windowName(hwnd):
    """
    从窗口句柄获取窗口名称
    返回None为不存在的窗口
    """
    try:
        title = win32gui.GetWindowText(hwnd)
        if not title or title=="":
            title="无标题窗口"
    except:
        title=None
    return title

def getAllWindows()-> list[WindowInfo]:
    # 获取所有窗口信息
    def enumHandler(hwnd, windows:list[WindowInfo]):
        if win32gui.IsWindowVisible(hwnd):
            title = hwnd2windowName(hwnd)
            
            pid = win32process.GetWindowThreadProcessId(hwnd)[1]
            process_name = psutil.Process(pid).name()
            process_path = psutil.Process(pid).exe()
            
            windows.append(WindowInfo(
                title=title, 
                hwnd=int(hwnd), 
                process=process_name, 
                PID=int(pid), 
                path=process_path
            ))
        return True

    windows = []
    win32gui.EnumWindows(enumHandler, windows)
    windows.sort(key=lambda x: x.title)

    return windows

def isSameWindow(w1:WindowInfo, w2:WindowInfo, auto=False, strict=True):
    """
    判断两个窗口的信息是否指向同一个窗口
    w1、w2: WindowInfo对象或字典
    auto: 智能匹配模式，默认False
    strict: 严格模式，默认True，非严格模式下只判断进程名称是否相同
    """
    # 转换可能的字典为WindowInfo对象
    if isinstance(w1, dict):
        w1 = WindowInfo.from_dict(w1)
    if isinstance(w2, dict):
        w2 = WindowInfo.from_dict(w2)
    
    ## 一模一样的两个，肯定是同一个
    if w1 == w2:
        return True
    
    process_except=["explorer.exe"]

    hwnd_same = w1.hwnd == w2.hwnd
    title_same = w1.title == w2.title and w1.title != "无标题窗口"
    process_name_same = w1.process == w2.process and w1.process not in process_except
    process_path_same = w1.path == w2.path
    PID_same = w1.PID == w2.PID
    process_same = process_name_same or PID_same

    ## 非严格模式下
    if not strict:
        ## 进程名称、路径相同则同一个
        if process_name_same and process_path_same:
            return True
    return False

    ## 非智能模式下
    if not auto:
        ## 进程名称相同且标题名称相同则同一个
        if process_name_same and title_same:
            return True
        ## 窗口句柄相同
        if hwnd_same:
            return True
        
    ## 如果两个窗口句柄相同，并且进程相同，则视为同一个
    if hwnd_same and process_same:
        return True

    ## 进程相同，并且窗口标题相同，则视为同一个
    if process_same and title_same:
        return True

    return False

def sendNotify(title,message):
    notify = wx.adv.NotificationMessage(title=title,message=message,parent=None)
    notify.SetIcon(wx.Icon(wx.Image(Config.icon).ConvertToBitmap()))
    try:
        notify.UseTaskBarIcon(Config.TaskBarIcon)
    except:
        pass
    notify.Show(timeout=3) # 1 for short timeout, 100 for long timeout

def keyMux(key):
    """
    按键多合一
    """
    
    key_name = key.name.lower()
    for n,v in vkMap.ScanName2VKName.items():
        if key_name == n.lower():
            return v
        
    return key_name.upper()

def keyConvert(hotkeys: dict):
    """
    按键解析

    传入：
    hotkeys: dict，键为热键，值为函数
    """
    expanded_hotkeys = {}
    need_check = {}
    flag = True
    # 将self.hotkeys中的每一项的键修改为小写
    for hotkey, action in hotkeys.items():
        hotkey = hotkey.lower()
        need_check[hotkey] = action
    while flag:
        flag = False
        this_round = need_check.copy()
        function_keys=[
            'ctrl','alt','shift','esc','enter','cmd','page_up',
            'page_down','home','end','insert','delete','backspace',
            'space','up','down','left','right','tab','caps_lock',
            'num_lock','scroll_lock','print_screen','pause','menu'
        ]
        for i in range(1,13):
            function_keys.append(f'f{i}')
        for hotkey, action in this_round.items():
            hotkey = hotkey.lower()
            keys = hotkey.split('+')
            intersect = list(set(keys) & set(function_keys))
            if len(intersect)>=1:
                i=intersect[0]
                del need_check['+'.join(keys)]
                keys.remove(i)
                keys.append(f"<{i}>")
                need_check['+'.join(keys)] = action
                flag = True
                continue

            if 'win' in keys:
                del need_check['+'.join(keys)]
                keys.remove('win')
                keys.append('<cmd>')
                need_check['+'.join(keys)] = action
                flag = True
                continue
            else:
                expanded_hotkeys['+'.join(keys)] = action

    return expanded_hotkeys